/*            Copyright(c) 2006-2008 Dave Sexton           *
 * http://www.codeplex.com/DocProject                      *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Xml.XPath;
using System.IO;

namespace DaveSexton.DocProject.Sandcastle.TopicManagement
{
	public sealed class TopicsDocument
	{
		#region Public Properties
		public static readonly string EmptyTopicsXml = "<?xml version=\"1.0\"?>" + Environment.NewLine + "<topics />";
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1802:UseLiteralsWhereAppropriate")]
		public static readonly string ApiRootPlaceholderElementName = "stoc";

		public string FilePath
		{
			get
			{
				return topicsFile;
			}
		}

		public IXPathNavigable Root
		{
			get
			{
				return document.DocumentElement;
			}
		}

		public IXPathNavigable ApiRoot
		{
			get
			{
				return FindApiRoot(document.DocumentElement);
			}
		}
		#endregion

		#region Private / Protected
		private readonly string topicsFile;
		private readonly XmlDocument document = new XmlDocument();
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="TopicsDocument" /> class.
		/// </summary>
		public TopicsDocument(string topicsFile)
		{
			if (string.IsNullOrEmpty(topicsFile))
				throw new ArgumentException(Resources.Errors.NullOrEmpty, "topicsFile");

			this.topicsFile = topicsFile;
		}
		#endregion

		#region Methods
		[System.Diagnostics.DebuggerHidden]
		public static bool HasConceptualTopics(string topicsFile)
		{
			try
			{
				using (XmlReader reader = CreateXmlReader(topicsFile))
				{
					if (reader == null)
						return false;

					reader.ReadStartElement("topics");

					while (!reader.EOF)
					{
						if (reader.NodeType == XmlNodeType.Element)
						{
							if (!string.Equals(reader.LocalName, ApiRootPlaceholderElementName, StringComparison.Ordinal))
								return true;
							else
								reader.Skip();
						}
						else
							reader.Read();
					}
				}
			}
			catch (System.Xml.XmlException) { }

			return false;
		}

		public static IEnumerable<string> GetAllFiles(string topicsFile)
		{
			using (XmlReader reader = CreateXmlReader(topicsFile))
			{
				if (reader == null)
					yield break;

				reader.ReadStartElement("topics");

				while (!reader.EOF)
				{
					if (reader.NodeType == XmlNodeType.Element)
					{
						if (!string.Equals(reader.LocalName, ApiRootPlaceholderElementName, StringComparison.Ordinal))
						{
							string relativePath = reader.GetAttribute("file");

							if (!string.IsNullOrEmpty(relativePath))
								yield return relativePath;		// NOTE: There won't be a file extension so the caller must add it

							reader.Read();
						}
						else
							reader.Skip();
					}
					else
						reader.Read();
				}
			}
		}

		[System.Diagnostics.DebuggerHidden]
		private static XmlReader CreateXmlReader(string topicsFile)
		{
			try
			{
				XmlReaderSettings settings = new XmlReaderSettings();
				settings.IgnoreComments = true;
				settings.IgnoreProcessingInstructions = true;
				settings.IgnoreWhitespace = true;

				return XmlReader.Create(topicsFile, settings);
			}
			catch (FileNotFoundException) { }
			catch (DirectoryNotFoundException) { }

			return null;
		}

		public static bool IsApiRoot(IXPathNavigable element)
		{
			if (element == null)
				return false;

			XPathNavigator navigator = element.CreateNavigator();

			return string.Equals(navigator.LocalName, ApiRootPlaceholderElementName, StringComparison.Ordinal)
				|| string.Equals(navigator.GetAttribute("apiRoot", ""), "true", StringComparison.OrdinalIgnoreCase);
		}

		public static IXPathNavigable FindApiRoot(IXPathNavigable element)
		{
			if (element == null)
				throw new ArgumentNullException("element");

			if (IsApiRoot(element))
				return element;
			else
				return FindApiRootRecursive(element);
		}

		private static IXPathNavigable FindApiRootRecursive(IXPathNavigable element)
		{
			foreach (XPathNavigator child in element.CreateNavigator().SelectChildren(XPathNodeType.Element))
			{
				if (IsApiRoot(child))
					return child;
				else
				{
					IXPathNavigable root = FindApiRootRecursive(child);

					if (root != null)
						return root;
				}
			}

			return null;
		}

		public static bool IsApiDescendant(IXPathNavigable element)
		{
			if (element == null)
				throw new ArgumentNullException("element");

			XPathNavigator navigator = element.CreateNavigator();

			while (navigator.MoveToParent())
			{
				if (IsApiRoot(navigator))
					return true;
			}

			return false;
		}

		public static void MakeApiRoot(IXPathNavigable element)
		{
			if (element == null)
				throw new ArgumentNullException("element");

			if (!IsApiRoot(element))
			{
				XPathNavigator navigator = element.CreateNavigator();
				navigator.CreateAttribute(null, "apiRoot", null, "true");
			}
		}

		public static bool ContainsApiRoot(IXPathNavigable element)
		{
			if (element == null)
				throw new ArgumentNullException("element");

			return ContainsApiRootRecursive(element.CreateNavigator());
		}

		private static bool ContainsApiRootRecursive(XPathNavigator element)
		{
			foreach (XPathNavigator child in element.SelectChildren(XPathNodeType.Element))
			{
				if (IsApiRoot(child) || ContainsApiRootRecursive(child))
					return true;
			}

			return false;
		}

		public bool HasConceptualTopics()
		{
			XPathNavigator topics = document.DocumentElement.CreateNavigator();

			foreach (XPathNavigator element in topics.SelectDescendants(XPathNodeType.Element, false))
			{
				if (!string.Equals(element.LocalName, ApiRootPlaceholderElementName, StringComparison.Ordinal))
					return true;
			}

			return false;
		}

		private XPathNavigator CreateApiRoot()
		{
			XPathNavigator api = document.CreateElement(null, ApiRootPlaceholderElementName, null).CreateNavigator();
			api.CreateAttribute(null, "project", null, "Project");

			return api;
		}

		[System.Diagnostics.DebuggerHidden]
		public void Load(bool apiRootRequired)
		{
			try
			{
				document.Load(topicsFile);
			}
			catch (System.IO.FileNotFoundException)
			{
				document.LoadXml(EmptyTopicsXml);
			}
			catch (System.IO.DirectoryNotFoundException)
			{
				document.LoadXml(EmptyTopicsXml);
			}

			LoadApiRoot(apiRootRequired);
		}

		private void LoadApiRoot(bool required)
		{
			XPathNavigator api = document.CreateNavigator().SelectSingleNode("//" + ApiRootPlaceholderElementName);

			if (required)
			{
				if (api == null)
				{
					api = document.DocumentElement.CreateNavigator();
					api.AppendChild(CreateApiRoot());
				}
			}
			else if (api != null)
			{
				api.DeleteSelf();
			}
		}

		public void Save()
		{
			XmlWriterSettings settings = new XmlWriterSettings();
			settings.Indent = true;
			settings.Encoding = Encoding.UTF8;

			using (XmlWriter writer = XmlWriter.Create(topicsFile, settings))
			{
				writer.WriteStartDocument();

				WriteElementsRecursive(writer, document.DocumentElement);

				writer.WriteEndDocument();
			}
		}

		private void WriteElementsRecursive(XmlWriter writer, XmlElement element)
		{
			writer.WriteStartElement(element.Name);

			foreach (XmlAttribute attribute in element.Attributes)
				writer.WriteAttributeString(attribute.LocalName, attribute.Value);

			bool foundApiRoot = false;

			foreach (XmlNode node in element.ChildNodes)
			{
				XmlElement child = node as XmlElement;

				if (child != null)
				{
					if (!foundApiRoot && IsApiRoot(child))
					{
						writer.WriteStartElement(ApiRootPlaceholderElementName);
						writer.WriteAttributeString("project", "Project");
						writer.WriteEndElement();

						foundApiRoot = true;
					}
					else
						WriteElementsRecursive(writer, child);
				}
			}

			writer.WriteEndElement();
		}

		public void Save(string file)
		{
			XmlWriterSettings settings = new XmlWriterSettings();
			settings.Indent = true;
			settings.Encoding = Encoding.UTF8;

			using (XmlWriter writer = XmlWriter.Create(file, settings))
			{
				document.Save(writer);
			}
		}
		#endregion
	}
}
